package simulator;

import java.util.*;
import java.io.*;
import java.io.FileReader;
import java.util.logging.Level;
import java.util.logging.Logger;


/**
 * Classe que representa el sistema operativo
 * 
 * por ahora solo puede manejar un solo proceso
 * cargando la memori de datos y de instfrucciones de este.
 * @author Administrador
 */
public class OperatingSystem {	
    
    private static OperatingSystem instance;
       
    private Processor processor;
    private Scheduler scheduler;
    private ProcessControllBlock[] pcbs;
    private int processCounter;    
    private ProcessControllBlock actualProcess;
    private final int QUANTUM = 8;
    private int tickCounter = 0;
     
    /**
     * Costructor
     */
    private OperatingSystem() throws OutOfProcessesException{
        this.processor = Processor.getInstance();
        this.pcbs = new ProcessControllBlock[32]; //32 diferentes procesos con 128 celdas de memoria cada uno
        scheduler = RoundRobinPrioQueueScheduler.getInstance();
        //System.out.println("scheduler created: \n"+scheduler);
        processCounter = 1;
        createEmptyProcess();
        //System.out.println("scheduler with empty process: \n"+scheduler);
        actualProcess = scheduler.getNextJob();
        
    }
    
    /**
     * Metodo para obtener la instancia
     */
    public static OperatingSystem getInstance() throws OutOfProcessesException{
        if(instance == null){
            instance = new OperatingSystem();
            return instance;
            
        }else{
            return instance;
        }
    }
    /**
     * metodo que crea el proceso vacio
     */
    protected void createEmptyProcess(){
        Ram.getInstance().setDataField("008", "0001");//ciclo infinito
        Ram.getInstance().setInstructionField("008", "1000");
        Ram.getInstance().setInstructionField("009", "3000");
        String SP = "7F";
        String PC = "000";
        String IB = "008";
        String DB = "008";
        SP = processor.pushOnStack(SP,PC);
        SP = processor.pushOnStack(SP,IB);
        SP = processor.pushOnStack(SP,DB);
        ProcessControllBlock result = new ProcessControllBlock(SP,10,0);
        pcbs[0] = result;
        scheduler.insertEmptyProcess(result);
    }
    public String getSchedulerState(){
        return scheduler.toString();
    }
    /**
     * Este metodo crea un proceso y le asigna su memoria, adicionalmente crea un PCB y lo guarda en 
     * la lista de PCB (pcbs)
     */
    public void createProcess(String instructions, String data){
        
        int dirBase = processCounter * 128;        
        
        /**
         * La información del proceso y insertar en su stack 
         * los registros nesesarios
         */
        String SP = Integer.toHexString(dirBase + 127);
        String PC = "000";        
        String IB = Integer.toHexString(dirBase);
        String DB = Integer.toHexString(dirBase);
        SP = processor.pushOnStack(SP, PC);
        SP = processor.pushOnStack(SP, IB);
        SP = processor.pushOnStack(SP, DB);
        ProcessControllBlock pcb = new ProcessControllBlock(SP,0,processCounter);
        
        int intIB = dirBase;
        int intDB = dirBase;
        
        BufferedReader inFile = null;
        try {
            inFile = new BufferedReader(new FileReader(data));
            while (inFile.ready()) {                
                String line = inFile.readLine();
                Ram.getInstance().setDataField(intDB, line);
                intDB++;
            }
        } catch (IOException ex) {
            Logger.getLogger(OperatingSystem.class.getName()).log(Level.SEVERE, null, ex);        
        } finally {
            try {
                inFile.close();
            } catch (IOException ex) {
                Logger.getLogger(OperatingSystem.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
        inFile = null;
        try {
            inFile = new BufferedReader(new FileReader(instructions));
            while (inFile.ready()) {                
                String line = inFile.readLine();
                Ram.getInstance().setInstructionField(intIB, line);
                intIB++;
            }
        } catch (IOException ex) {
            Logger.getLogger(OperatingSystem.class.getName()).log(Level.SEVERE, null, ex);          
        } finally {
            try {
                inFile.close();
            } catch (IOException ex) {
                Logger.getLogger(OperatingSystem.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
        this.pcbs[processCounter]=pcb;
        processCounter++;
        //System.out.println("inserting process: " + pcb);
        //System.out.println("BEFORE NEW process: \n"+scheduler);
        scheduler.insertProcess(pcb);
        //System.out.println("inserted new process: \n"+scheduler);
    }  
    
    /**
     * Metodo que avanza un ciclo de ejecucion en el procesador
     * Nota: La interrupción 0 se usa para el sistema opertivo!!!
     * 
     */
    public void advanceStep() throws AddressOverFlowException, PcOverFlowException, StackOverflowException, StackUnderFlowException, OpperationNotExistsException, UnsuportedItException, OutOfProcessesException{
        processor.fetch();                
        try{
            processor.execute();
        }catch (ProgrammEndedException e){
            finishProcess();
        }
        if(tickCounter == QUANTUM){
            //System.out.println("entered interrupt");
            interrupt(0);
            tickCounter = 0;
        }
        tickCounter++;
        processor.handelInterrupt();
        
    }
    /**
     * Metodo que se llama un unica vez para iniciar la ejecución del procesador
     */
    public void initProcessor() throws StackUnderFlowException{
        processor.SP = actualProcess.SP;
        processor.rit();
    }
    /**
     * Metodo para realizar una interrupción en el procesador
     * @param el numero de la interrupcion
     */
    public void interrupt(int itNr) throws UnsuportedItException{
        processor.insertInterrupt(itNr);
    }   
    /**
     * Carga un archivo en el que estan espesificados en este orden
     * el archivo para los datos xxx.dat
     * el archivo para las instrucciones xxx.ins
     * el archivo para las interrupciones xxx.ins
     * @param El nombre del archivo
     */
    public void loadLotFile(String fileName){
        BufferedReader inFile = null;
        try {
            inFile = new BufferedReader(new FileReader(fileName));
            String actualInstructionFile = null;   
            String line = inFile.readLine();
            while(line != null){
                String[] actual = line.split("=");
                if(actual[0].equals("IT")){
                    //System.out.println("loding ITs");
                    loadInterruptFile(actual[1]);
                }   
                if(actual[0].equals("INS")){
                    //System.out.println("loding ins file");
                    actualInstructionFile = actual[1];
                }
                if(actual[0].equals("DAT")){
                    if(actualInstructionFile != null){
                        createProcess(actualInstructionFile, actual[1]);
                    }
                }
                line = inFile.readLine();                        
            }
        }catch(IOException ex) {
            Logger.getLogger(OperatingSystem.class.getName()).log(Level.SEVERE, null, ex);          
        } finally {
            try {
                inFile.close();
            } catch (IOException ex) {
                Logger.getLogger(OperatingSystem.class.getName()).log(Level.SEVERE, null, ex);
            }
        }                
    }
    
    public String getProcessorState(){
        
        String result = "--------------\n";
        result +=   processor.toString();
        result += "\n--------------\n";
        return result;
    }  
    
    /**
     * Metodo que carga las Interupciones a Memoria.
     * Las interupciones usan el quarto final de la memoria 
     * de instrucciones.
     * es decir desde la direccion 3072.
     * @param el nombre del archivo
     */
    public void loadInterruptFile(String fileName) {
        BufferedReader inFile = null;
        try {
            inFile = new BufferedReader(new FileReader(fileName));
            int intItRealAddress = 16;//primera dirección libre en el 1 processo (Empty process)
            int itNr = 0;
            while (inFile.ready()) {                
                String line = inFile.readLine();
                if(line.contains("IT")){
                    //System.out.println("itNr = "+itNr);
                    //System.out.println("intItRealAdress = "+intItRealAddress);
                    Ram.getInstance().setInstructionField(itNr, Integer.toHexString(intItRealAddress));
                    //System.out.println(Integer.toHexString(intItRealAddress));
                    //System.out.println("actual IT address = " +Ram.getInstance().getInstruction(itNr));
                    itNr++;                    
                }else{
                    Ram.getInstance().setInstructionField(intItRealAddress, line);
                    intItRealAddress++;
                }
            }
        } catch (IOException ex) {
            Logger.getLogger(OperatingSystem.class.getName()).log(Level.SEVERE, null, ex);          
        } finally {
            try {
                inFile.close();
            } catch (IOException ex) {
                Logger.getLogger(OperatingSystem.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
    }
    public void loadNextProcess() throws OutOfProcessesException{
        //System.out.println("Loading next process: \n actual Process:\n" +actualProcess);
        actualProcess = scheduler.nextJob(actualProcess);
        System.out.println("next Process:\n" +actualProcess);
        processor.SP = actualProcess.SP;
    }
    public void finishProcess(){
        actualProcess = scheduler.nextJob();
        processor.SP = actualProcess.SP;
    }
    
}
